// 主文件
use askama::Template;
use axum::{
    http::StatusCode,
    response::{Html, IntoResponse, Response},
    routing::get,
    Router,
};
use axum_csrf::{CsrfConfig, SameSite};
use axum_session::{SessionConfig, SessionRedisPool, SessionStore};
use chrono::Duration;
use rabbit::{
    handlers::{books, chats, h5web, identcode, mp},
    settings::Settings,
    AppState, WsClient,
};

use sqlx::sqlite::SqlitePoolOptions;
use tower_http::trace::{DefaultMakeSpan, TraceLayer};

use std::{
    collections::HashMap,
    env,
    net::SocketAddr,
    sync::{Arc, Mutex},
};
use tokio::{signal, sync::broadcast};

use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};

#[tokio::main]
async fn main() {
    // 从环境变量读取监听地址
    let addr = env::args()
        .nth(1)
        .unwrap_or_else(|| "127.0.0.1:24011".to_string());
    // 配置trace
    tracing_subscriber::registry()
        .with(
            tracing_subscriber::EnvFilter::try_from_default_env()
                .unwrap_or_else(|_| "rabbit=debug".into()),
        )
        .with(tracing_subscriber::fmt::layer())
        .init();
    // 实例化配置文件
    let settings = Settings::new().expect("unable to load configuration file");
    // 读取sqlite的路径
    let db_connection_str = settings.database.dsn.clone();
    // 获取sqlite的连接池
    let db_pool = SqlitePoolOptions::new()
        .connect(&db_connection_str)
        .await
        .expect("unable to connect to database");
    // 读取redis的连接地址
    let redis_connection_str = settings.redis.url.clone();
    // 获取redis连接客户端
    let redis_client =
        redis::Client::open(redis_connection_str).expect("unable to connet to redis");

    let peer_map = Mutex::new(HashMap::new());
    let (tx, _rx) = broadcast::channel(100);
    let ws_client_state = Arc::new(WsClient { peer_map, tx });
    // 构造共享状态
    let shared_state = Arc::new(AppState {
        db: db_pool,
        conf: settings,
        rs: redis_client.clone(),
        ws: ws_client_state,
    });

    let session_config = SessionConfig::default()
        .with_cookie_same_site(SameSite::Strict)
        .with_secure(true)
        .with_cookie_name("rabbit")
        .with_lifetime(Duration::days(1))
        .with_max_age(Some(Duration::days(1)));
    let session_store =
        SessionStore::<SessionRedisPool>::new(Some(redis_client.clone().into()), session_config)
            .await
            .unwrap();

    let csrf_config = CsrfConfig::default();

    let book_routers = books::router(Arc::clone(&shared_state), session_store, csrf_config);
    let mp_routers = mp::router(Arc::clone(&shared_state));
    let identcode_routers = identcode::router(Arc::clone(&shared_state));
    let chat_routers = chats::router(Arc::clone(&shared_state));
    let h5web_routers = h5web::router(Arc::clone(&shared_state));
    // build a route for application
    let app = Router::new()
        .route("/", get(index))
        .route("/ping", get(ping))
        .merge(book_routers) // 使用merge的好处，是使用各自的中间件
        .merge(mp_routers)
        .merge(identcode_routers)
        .merge(chat_routers)
        .merge(h5web_routers)
        .layer(
            TraceLayer::new_for_http()
                .make_span_with(DefaultMakeSpan::default().include_headers(true)),
        );
    let app = app.fallback(handler_404);
    // run it
    tracing::debug!("listening on {}", addr);
    let saddr: SocketAddr = addr.parse().expect("unable to parse socket address");
    axum::Server::bind(&saddr)
        .serve(app.into_make_service_with_connect_info::<SocketAddr>())
        .with_graceful_shutdown(shutdown_signal())
        .await
        .unwrap();
}
// 可检查服务存活
async fn ping() -> &'static str {
    "pong"
}

async fn handler_404() -> impl IntoResponse {
    (StatusCode::NOT_FOUND, "nothing to see here")
}

// 首页
#[derive(Template)]
#[template(path = "frontend/index.html")]
struct IndexTemplate<'a> {
    title: &'a str,
}

struct HtmlTemplate<T>(T);

impl<T> IntoResponse for HtmlTemplate<T>
where
    T: Template,
{
    fn into_response(self) -> Response {
        match self.0.render() {
            Ok(html) => Html(html).into_response(),
            Err(err) => (
                StatusCode::INTERNAL_SERVER_ERROR,
                format!("Failed to render template. Error: {}", err),
            )
                .into_response(),
        }
    }
}

async fn index() -> impl IntoResponse {
    let template = IndexTemplate { title: "首页" };
    HtmlTemplate(template)
}
// 平滑退出
async fn shutdown_signal() {
    let ctrl_c = async {
        signal::ctrl_c()
            .await
            .expect("failed to install Ctrl+C handler");
    };

    #[cfg(unix)]
    let terminate = async {
        signal::unix::signal(signal::unix::SignalKind::terminate())
            .expect("failed to install signal handler")
            .recv()
            .await;
    };

    #[cfg(not(unix))]
    let terminate = std::future::pending::<()>();

    tokio::select! {
        _ = ctrl_c => {},
        _ = terminate => {},
    }

    println!("signal received, starting graceful shutdown");
}
